#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs2.h>

LIST_HEAD(super_blocks);

static struct super_block *alloc_super(struct file_system_type *type)
{
	// struct super_block *s = kzalloc(sizeof(struct super_block),  GFP_USER);
	struct super_block *s = kmalloc(sizeof(struct super_block));
	// static const struct super_operations default_op;

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	if (s) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		// if (security_sb_alloc(s)) {
		// 	kfree(s);
		// 	s = NULL;
		// 	goto out;
		// }
		INIT_LIST_HEAD(&s->s_files);
		INIT_LIST_HEAD(&s->s_instances);
		// INIT_HLIST_HEAD(&s->s_anon);
		INIT_LIST_HEAD(&s->s_inodes);
		INIT_LIST_HEAD(&s->s_dentry_lru);
		// init_rwsem(&s->s_umount);
		// mutex_init(&s->s_lock);
		// lockdep_set_class(&s->s_umount, &type->s_umount_key);
		/*
		 * The locking rules for s_lock are up to the
		 * filesystem. For example ext3fs has different
		 * lock ordering than usbfs:
		 */
		// lockdep_set_class(&s->s_lock, &type->s_lock_key);
		/*
		 * sget() can have s_umount recursion.
		 *
		 * When it cannot find a suitable sb, it allocates a new
		 * one (this one), and tries again to find a suitable old
		 * one.
		 *
		 * In case that succeeds, it will acquire the s_umount
		 * lock of the old one. Since these are clearly distrinct
		 * locks, and this object isn't exposed yet, there's no
		 * risk of deadlocks.
		 *
		 * Annotate this by putting this lock in a different
		 * subclass.
		 */
		// down_write_nested(&s->s_umount, SINGLE_DEPTH_NESTING);
		// s->s_count = S_BIAS;
		// atomic_set(&s->s_active, 1);
		// mutex_init(&s->s_vfs_rename_mutex);
		// mutex_init(&s->s_dquot.dqio_mutex);
		// mutex_init(&s->s_dquot.dqonoff_mutex);
		// init_rwsem(&s->s_dquot.dqptr_sem);
		// init_waitqueue_head(&s->s_wait_unfrozen);
		s->s_maxbytes = MAX_NON_LFS;
		// s->dq_op = sb_dquot_ops;
		// s->s_qcop = sb_quotactl_ops;
		// s->s_op = &default_op;
		s->s_time_gran = 1000000000;
	}
out:
	return s;
}

struct super_block *sget(struct file_system_type *type,
			int (*test)(struct super_block *,void *),
			int (*set)(struct super_block *,void *),
			void *data)
{
	struct super_block *s = NULL;
	// struct super_block *old;
	int err;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

retry:
	// spin_lock(&sb_lock);
	// if (test) {
	// 	list_for_each_entry(old, &type->fs_supers, s_instances) {
	// 		if (!test(old, data))
	// 			continue;
	// 		if (!grab_super(old))
	// 			goto retry;
	// 		if (s) {
	// 			up_write(&s->s_umount);
	// 			destroy_super(s);
	// 		}
	// 		return old;
	// 	}
	// }
	if (!s) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		// spin_unlock(&sb_lock);
		s = alloc_super(type);
		if (!s)
			return ERR_PTR(-ENOMEM);
		// goto retry;
	}
		
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// err = set(s, data);
	// if (err) {
		// spin_unlock(&sb_lock);
		// up_write(&s->s_umount);
		// destroy_super(s);
		// return ERR_PTR(err);
	// }
	s->s_type = type;
	// strlcpy(s->s_id, type->name, sizeof(s->s_id));
	list_add_tail(&s->s_list, &super_blocks);
	list_add(&s->s_instances, &type->fs_supers);
	// spin_unlock(&sb_lock);
	// get_filesystem(type);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return s;
}

int get_sb_nodev(struct file_system_type *fs_type,
	int flags, void *data,
	int (*fill_super)(struct super_block *, void *, int),
	struct vfsmount *mnt)
{
	int error;
	// struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
	struct super_block *s = sget(fs_type, NULL, NULL, NULL);

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	if (IS_ERR(s))
		return PTR_ERR(s);

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	s->s_flags = flags;

	error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
	if (error) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		// deactivate_locked_super(s);
		return error;
	}
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	s->s_flags |= MS_ACTIVE;
	simple_set_mnt(mnt, s);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return 0;
}

int get_sb_bdev(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data,
	int (*fill_super)(struct super_block *, void *, int),
	struct vfsmount *mnt)
{
	// struct block_device *bdev;
	struct super_block *s;
	// fmode_t mode = FMODE_READ;
	int error = 0;

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// if (!(flags & MS_RDONLY))
	// 	mode |= FMODE_WRITE;

	// bdev = open_bdev_exclusive(dev_name, mode, fs_type);
	// if (IS_ERR(bdev))
	// 	return PTR_ERR(bdev);

	/*
	 * once the super is inserted into the list by sget, s_umount
	 * will protect the lockfs code from trying to start a snapshot
	 * while we are mounting
	 */
	// mutex_lock(&bdev->bd_fsfreeze_mutex);
	// if (bdev->bd_fsfreeze_count > 0) {
	// 	mutex_unlock(&bdev->bd_fsfreeze_mutex);
	// 	error = -EBUSY;
	// 	goto error_bdev;
	// }
	// s = sget(fs_type, test_bdev_super, set_bdev_super, bdev);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	s = sget(fs_type, NULL, NULL, NULL);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// mutex_unlock(&bdev->bd_fsfreeze_mutex);
	if (IS_ERR(s))
		goto error_s;

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	if (s->s_root) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		// if ((flags ^ s->s_flags) & MS_RDONLY) {
		// 	deactivate_locked_super(s);
		// 	error = -EBUSY;
		// 	goto error_bdev;
		// }

		// close_bdev_exclusive(bdev, mode);
	} else {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		char b[BDEVNAME_SIZE];

		s->s_flags = flags;
		// s->s_mode = mode;
		// strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id));
		// sb_set_blocksize(s, block_size(bdev));
		error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
		if (error) {
			// deactivate_locked_super(s);
			goto error;
		}

		s->s_flags |= MS_ACTIVE;
		// bdev->bd_super = s;
	}

	simple_set_mnt(mnt, s);
	return 0;

error_s:
	error = PTR_ERR(s);
error_bdev:
	// close_bdev_exclusive(bdev, mode);
error:
	return error;
}

struct vfsmount *vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
	struct vfsmount *mnt;
	char *secdata = NULL;
	int error;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	if (!type)
		return ERR_PTR(-ENODEV);

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	error = -ENOMEM;
	mnt = alloc_vfsmnt(name);
	if (!mnt)
		goto out;

	// if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {
	// 	secdata = alloc_secdata();
	// 	if (!secdata)
	// 		goto out_mnt;

	// 	error = security_sb_copy_data(data, secdata);
	// 	if (error)
	// 		goto out_free_secdata;
	// }

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	error = type->get_sb(type, flags, name, data, mnt);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	if (error < 0)
		goto out_free_secdata;
	// BUG_ON(!mnt->mnt_sb);
	printf("this is %s(): %d\r\n", __func__, __LINE__);

 	// error = security_sb_kern_mount(mnt->mnt_sb, flags, secdata);
 	// if (error)
 	// 	goto out_sb;

	/*
	 * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
	 * but s_maxbytes was an unsigned long long for many releases. Throw
	 * this warning for a little while to try and catch filesystems that
	 * violate this rule. This warning should be either removed or
	 * converted to a BUG() in 2.6.34.
	 */
	// WARN((mnt->mnt_sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "
	// 	"negative value (%lld)\n", type->name, mnt->mnt_sb->s_maxbytes);

	mnt->mnt_mountpoint = mnt->mnt_root;
	mnt->mnt_parent = mnt;
	// up_write(&mnt->mnt_sb->s_umount);
	// free_secdata(secdata);
	return mnt;
out_sb:
	// dput(mnt->mnt_root);
	// deactivate_locked_super(mnt->mnt_sb);
out_free_secdata:
	// free_secdata(secdata);
out_mnt:
	// free_vfsmnt(mnt);
out:
	return ERR_PTR(error);
}

struct vfsmount *do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
	struct file_system_type *type = get_fs_type(fstype);
	struct vfsmount *mnt;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	if (!type)
		return ERR_PTR(-ENODEV);

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	mnt = vfs_kern_mount(type, flags, name, data);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) && !mnt->mnt_sb->s_subtype)
	// 	mnt = fs_set_subtype(mnt, fstype);

	// put_filesystem(type);

	return mnt;
}
